//
//  tftp.h
//  TFTPDemo
//
//  Created by 陈冠杰 on 16/8/1.
//  Copyright © 2016年 EzioChen. All rights reserved.
//

#ifndef tftp_h
#define tftp_h

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>

#if defined(SUNOS_5)
#define OS_SUNOS5

typedef short u_int16_t;
typedef int u_int32_t;
typedef char u_int8_t;
#elif defined(__GNUC__) && (defined(__APPLE_CPP__) || defined(__APPLE_CC__) || defined(__MACOS_CLASSIC__))
#define OS_MACOSX

#elif defined(__GNUC__) && defined(__linux__)
#define OS_LINUX


#else
#error "Unsupported operating system"

#endif


#define BLOCK_SIZE_GET  16384
#define BLOCK_SIZE_PUT  512

#define OPCODE_RRQ   1
#define OPCODE_WRQ   2
#define OPCODE_DATA  3
#define OPCODE_ACK   4
#define OPCODE_ERR   5
#define OPCODE_OACK  6

#define MODE_NETASCII "netascii"
#define MODE_OCTET    "octet"
#define MODE_MAIL     "mail"

#define TFTP_PORT 20069

#define TFTP_TYPE_GET 0
#define TFTP_TYPE_PUT 1

/* Timeout in seconds */
#define TFTP_TIMEOUT 2

static char* err_codes[8] = {
    
    "Undef\n",
    "File not found\n",
    "Access violation\n",
    "Disk full or allocation exceeded\n",
    "Illegal TFTP operation\n",
    "Unknown transfer ID\n",
    "File already exists\n",
    "No such user\n"
    
};



/*
 A generic header for TFTP messages.
*/

struct tftp_msg {
    u_int16_t opcode;
    char msg[0];
};

/*
 A TFTP read request.
 */
struct tftp_rrq {
    u_int16_t opcode;
    char req[0];
};

#define TFTP_RRQ_HDR_LEN sizeof(struct tftp_rrq)
#define TFTP_RRQ_LEN(f,m,b,s) (sizeof(struct tftp_rrq) + strlen(f) + strlen(m) + strlen(s) + 4 + strlen(b))

/*
 A TFTP write request.
 */
struct tftp_wrq {
    u_int16_t opcode;
    char req[0];
};

#define TFTP_WRQ_HDR_LEN sizeof(struct tftp_wrq)
#define TFTP_WRQ_LEN(f,m,b,s) (sizeof(struct tftp_wrq) + strlen(f) + strlen(m) + strlen(s) + 4 + strlen(b))

/*
 A TFTP data block message.
 */
struct tftp_data {
    u_int16_t opcode;
    u_int16_t blocknr;
    char data[0];
};

#define TFTP_DATA_HDR_LEN sizeof(struct tftp_data)

/*
 A TFTP ack message.
 */
struct tftp_ack {
    u_int16_t opcode;
    u_int16_t blocknr;
};

#define TFTP_ACK_HDR_LEN sizeof(struct tftp_ack)

/*
 A TFTP error message.
 */
struct tftp_err {
    u_int16_t opcode;
    u_int16_t errcode;
    char errmsg[0];
};

#define TFTP_ERR_HDR_LEN sizeof(struct tftp_err)

static inline char *tftp_err_to_str(int err)
{
    if (err < 0 || err > 7)
        return NULL;
    
    return err_codes[err];
}

#define MSGBUF_SIZE (TFTP_DATA_HDR_LEN + BLOCK_SIZE_GET)
struct tftp_conn {
    
    int type;                       /* Are we putting or getting? */
    FILE *fp;                       /* The file we are reading or writing */
    int sock;                       /* Socket to communicate with server */
    int blocknr;                    /* The current block number */
    char *fname;                    /* The file name of the file we are putting or getting */
    char *mode;                     /* TFTP mode */
    struct sockaddr_in peer_addr;   /* Remote peer address */
    socklen_t addrlen;              /* The remote address length */
    char *msgbuf;       /* Buffer for messages being sent or received */
    FILE *fw;/**/
};

#endif /* TFTP_H_ */




int tftp_transfer(struct tftp_conn *tc);


/*
 * Send a data block to the other side.
 *1. Format message.
 *2. Add data block to message according to length argument.
 *3. Send the data block message using the connection handle.
 *4. Return the number of bytes sent, or negative on error.
 
 You need to be able to resend data in case of a timeout. When resending,
 the old message should be sent again and therefore on new message should be created.
 This can ,for example ,be handled by passing a negative length indicating that the creation of a new message
 should be skipped.
 */
int tftp_send_data(struct tftp_conn* tc,int length);



struct tftp_conn *tftp_connect(int type, char *fname, char *mode, const char *hostname, const short port);

/**
 *  close the connection handle,i.3, delete out local state
 */
void tftp_close(struct tftp_conn *tc);

void tftp_interrupt(struct tftp_conn *tc);

